﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using CommonBehaviors.Actions;
using Styx;
using Styx.Common;
using Styx.CommonBot;
using Styx.CommonBot.Frames;
using Styx.CommonBot.Profiles;
using Styx.Pathing;
using Styx.TreeSharp;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;
using Action = Styx.TreeSharp.Action;

namespace EtherealTheory.Honorbuddy.QuestBehaviors
{
    public class SunsongRanch : CustomForcedBehavior
    {
        private delegate T WoWObjectDelegate<T>(object context) where T : WoWObject;
        private delegate WoWPoint WoWPointDelegate(object context);
        private delegate bool BooleanDelegate(object context);

        /// <summary>
        /// Base TreeSharp node that instruments throttled behavior. Directives are only issued once per delay period. 
        /// </summary>
        private abstract class ThrottledActionBase : Composite
        {
            private TimeSpan _delay = TimeSpan.Zero;
            private Stopwatch _stopwatch = new Stopwatch();

            /// <summary>
            /// Initializes a new instance of the <see cref="ThrottledAction"/> with a <paramref name="delay"/>
            /// </summary>
            /// <param name="delay">Throttled <see cref="TimeSpan"/> for next execution.</param>
            public ThrottledActionBase(TimeSpan delay)
            {
                _delay = delay;
            }

            protected override IEnumerable<RunStatus> Execute(object context)
            {
                if (OnCanReset())
                    _stopwatch.Reset();

                if (OnCanExecute() && OnThrottledExecute(context))
                    _stopwatch.Start();

                yield return RunStatus.Success;
            }

            protected virtual bool OnCanExecute()
            {
                return !_stopwatch.IsRunning;
            }

            protected virtual bool OnCanReset()
            {
                return _stopwatch.IsRunning && _stopwatch.Elapsed > _delay;
            }

            private bool ThrottledExecute(object context)
            {
                return OnThrottledExecute(context);
            }

            protected abstract bool OnThrottledExecute(object context);
        }

        /// <summary>
        /// Base TreeSharp node that instruments throttled behavior based on a <see cref="WoWObject"/>. Directives are only issued once per delay period. 
        /// </summary>
        private abstract class WoWObjectAction<T> : ThrottledActionBase where T: WoWObject
        {
            private T _wowObject = null;
            private T _lastWoWObject = null;
            private WoWObjectDelegate<T> _wowObjectDelegate;

            /// <summary>
            /// Initializes a new instance of the <see cref="ThrottledAction"/> with a <paramref name="delay"/>
            /// </summary>
            /// <param name="delay">Throttled <see cref="TimeSpan"/> for next execution.</param>
            /// <param name="wowObjectDelegate">delegate to get the <see cref="WoWObject"/></param>
            public WoWObjectAction(TimeSpan delay, WoWObjectDelegate<T> wowObjectDelegate) 
                : base(delay)
            {
                if (null == wowObjectDelegate)
                    throw new ArgumentNullException("wowObjectDelegate");
                _wowObjectDelegate = wowObjectDelegate;
            }

            protected override IEnumerable<RunStatus> Execute(object context)
            {
                _wowObject = _wowObjectDelegate(context);
                if (null == _wowObject || !_wowObject.IsValid)
                {
                    Logging.WriteDiagnostic("WoWObjectAction Executed with invalid object");
                    return new HashSet<RunStatus> { RunStatus.Failure };
                }
                Logging.WriteDiagnostic("WoWObjectAction Execute: {0} {1} @ {2}", _wowObject.Name, _wowObject.Entry, _wowObject.Location);
                return base.Execute(context);
            }

            protected override bool OnCanReset()
            {
                return _lastWoWObject != WoWObject || base.OnCanReset();
            }

            protected override bool OnThrottledExecute(object context)
            {
                _lastWoWObject = WoWObject;
                return true;
            }

            protected T WoWObject 
            {
                get
                {
                    return _wowObject;
                }
            }
        }

        /// <summary>
        /// TreeSharp node that instruments throttled behavior that has the player interact with a <see cref="WoWObject"/>. Directives are only issued once per delay period. 
        /// </summary>
        private class InteractAction : WoWObjectAction<WoWObject>
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="ThrottledAction"/> with a <paramref name="delay"/> to interact with a <see cref="WoWObject"/>.
            /// </summary>
            /// <param name="delay">Throttled <see cref="TimeSpan"/> for next execution.</param>
            public InteractAction(TimeSpan delay, WoWObjectDelegate<WoWObject> wowObjectDelegate)
                : base(delay, wowObjectDelegate)
            {
            }

            protected override bool OnCanExecute()
            {
                return !StyxWoW.Me.IsMoving 
                    //&& !StyxWoW.Me.Combat
                    //&& !StyxWoW.Me.IsCasting
                    //&& !StyxWoW.Me.IsChanneling
                    //&& StyxWoW.Me.IsFacing(WoWObject)
                    //&& WoWObject.WithinInteractRange
                    && base.OnCanExecute();
            }

            protected override bool OnThrottledExecute(object context)
            {
                Logging.Write("Interacting with {0} {1} @ {2}", WoWObject.Name, WoWObject.Entry, WoWObject.Location);
                WoWObject.Interact();
                return base.OnThrottledExecute(context);
            }
        }

        /// <summary>
        /// TreeSharp node that instruments throttled behavior that has the player target a <see cref="WoWObject"/>. Directives are only issued once per delay period. 
        /// </summary>
        private class TargetAction : WoWObjectAction<WoWUnit>
        {
            private ActionSucceedDelegate _actionSucceedDelegate;

            /// <summary>
            /// Initializes a new instance of the <see cref="ThrottledAction"/> with a <paramref name="delay"/> to target a <see cref="WoWObject"/> and
            /// performs an <param name="wowObjectDelegate"/>/
            /// </summary>
            /// <param name="delay">Throttled <see cref="TimeSpan"/> for next execution.</param>
            public TargetAction(TimeSpan delay, WoWObjectDelegate<WoWUnit> wowObjectDelegate, ActionSucceedDelegate actionSucceedDelegate)
                : base(delay, wowObjectDelegate)
            {
                if (null == actionSucceedDelegate)
                    throw new ArgumentNullException("actionSucceedDelegate");

                _actionSucceedDelegate = actionSucceedDelegate;
            }

            protected override bool OnCanExecute()
            {
                return !StyxWoW.Me.IsMoving
                    //&& !StyxWoW.Me.Combat
                    //&& !StyxWoW.Me.IsCasting
                    //&& !StyxWoW.Me.IsChanneling
                    //&& StyxWoW.Me.IsFacing(WoWObject)
                    //&& WoWObject.WithinInteractRange
                    && base.OnCanExecute();
            }

            protected override bool OnThrottledExecute(object context)
            {
                if (StyxWoW.Me.CurrentTarget != WoWObject)
                {
                    WoWObject.Target();
                    return false;
                }
                Logging.Write("Targetted action on {0} {1} @ {2}", WoWObject.Name, WoWObject.Entry, WoWObject.Location);
                _actionSucceedDelegate(context);
                return base.OnThrottledExecute(context);
            }
        }

        /// <summary>
        /// TreeSharp node that instruments throttled behavior through delegates. Directives are only issued once per delay period. 
        /// </summary>
        private class ThrottledAction : ThrottledActionBase
        {
            private ActionSucceedDelegate _actionSucceedDelegate;

            /// <summary>
            /// Initializes a new instance of the <see cref="ThrottledAction"/> with a <paramref name="delay"/> and 
            /// <paramref name="actionSucceedDelegate"/>.
            /// </summary>
            /// <param name="delay">Throttled <see cref="TimeSpan"/> for next execution.</param>
            /// <param name="actionSucceedDelegate"><see cref="ActionSucceedDelegate"/> with directives.</param>
            public ThrottledAction(TimeSpan delay, ActionSucceedDelegate actionSucceedDelegate)
                : base(delay)
            {
                if (null == actionSucceedDelegate)
                    throw new ArgumentNullException("actionSucceedDelegate");
                _actionSucceedDelegate = actionSucceedDelegate;
            }

            protected override bool OnThrottledExecute(object context)
            {
                _actionSucceedDelegate(context);
                return true;
            }
        }

        /// <summary>
        /// TreeSharp node that instruments movement behavior towards a location and the
        /// ability to define stuck destination.
        /// </summary>
        private class MovementAction : Composite
        {
            public enum MovementType
            {
                //Flying, mounted or unmounted is irrelvant
                Irrelvant = 0,
                //Ensure to be dismounted when arriving
                Dismount,
                //Fly and then dismount at arrival
                FlyDismount,
                //Keep flying
                Fly
            }

            private bool _stuck = false;
            private Stopwatch stopwatch = new Stopwatch();
            private WoWPoint _startLocation = WoWPoint.Empty;
            private WoWPoint _targetLocation = WoWPoint.Empty;

            private TimeSpan _delay = TimeSpan.Zero;
            private WoWPointDelegate _locationDelegate = null;
            private WoWPointDelegate _stuckLocationDelegate = null;
            private WoWObjectDelegate<WoWObject> _objectDelegate = null;
            private MovementType _movementType;

            /// <summary>
            /// Initializes a new instance of the <see cref="MovementAction"/>. 
            /// </summary>
            /// <param name="flyDelegate">Fly or groud movement</param>
            /// <param name="delay">Time period in movement should be achieved or deemed stuck</param>
            /// <param name="stuckLocationDelegate">Stuck location to fly to</param>
            private MovementAction(MovementType movementType = MovementType.Irrelvant, TimeSpan delay = default(TimeSpan), WoWPointDelegate stuckLocationDelegate = null)
            {
                _movementType = movementType;
                _delay = delay;
                _stuckLocationDelegate = stuckLocationDelegate;
            }

            /// <summary>
            /// Initializes a new instance of the <see cref="MovementAction"/> to move to a location. 
            /// </summary>
            /// <param name="locationDelegate">Target location</param>
            /// <param name="flyDelegate">Fly or groud movement</param>
            /// <param name="delay">Time period in movement should be achieved or deemed stuck</param>
            /// <param name="stuckLocationDelegate">Stuck location to fly to</param>
            public MovementAction(WoWPointDelegate locationDelegate, MovementType movementType = MovementType.Irrelvant, TimeSpan delay = default(TimeSpan), WoWPointDelegate stuckLocationDelegate = null)
                : this(movementType, delay, stuckLocationDelegate)
            {
                _locationDelegate = locationDelegate;
            }

            /// <summary>
            /// Initializes a new instance of the <see cref="MovementAction"/> to move to interact distance of an object. 
            /// </summary>
            /// <param name="objectDelegate">Target object</param>
            /// <param name="flyDelegate">Fly or groud movement</param>
            /// <param name="delay">Time period in movement should be achieved or deemed stuck</param>
            /// <param name="stuckLocationDelegate">Stuck location to fly to</param>
            public MovementAction(WoWObjectDelegate<WoWObject> objectDelegate, MovementType movementType = MovementType.Irrelvant, TimeSpan delay = default(TimeSpan), WoWPointDelegate stuckLocationDelegate = null)
                : this(movementType, delay, stuckLocationDelegate)
            {
                _objectDelegate = objectDelegate;
            }

            protected override IEnumerable<RunStatus> Execute(object context)
            {
                //Which location are going for?
                WoWObject wowObject = null != _objectDelegate ? _objectDelegate(context) : null;
                WoWPoint location = _stuck
                    ? _stuckLocationDelegate(context)
                    : null != _locationDelegate
                        ? _locationDelegate(context)
                        : null != wowObject
                            ? wowObject.Location
                            : WoWPoint.Empty;

                //Should never happen
                if(WoWPoint.Empty == location)
                    yield return RunStatus.Failure;

                //Reset if target location has changed.
                if (_targetLocation != location)
                {
                    stopwatch.Reset();
                    _targetLocation = location;
                }

                //Have we arrived?
                if(null != wowObject ? wowObject.WithinInteractRange : StyxWoW.Me.Location.DistanceSqr(location) < 2)
                {
                    bool actionTaken = false;
                    if (ActionTaken(!StyxWoW.Me.IsFacing(location), ref actionTaken))
                        StyxWoW.Me.SetFacing(location);
                    if (ActionTaken(StyxWoW.Me.IsMoving, ref actionTaken))
                        Navigator.PlayerMover.MoveStop();
                    if (ActionTaken(StyxWoW.Me.Mounted && (_movementType == MovementType.Dismount || _movementType == MovementType.FlyDismount), ref actionTaken))
                        Mount.Dismount();

                    if(actionTaken)
                        yield return RunStatus.Success;

                    stopwatch.Reset();
                    _stuck = false;
                    yield return RunStatus.Failure;
                }

                //When delay has passed, calc stuck.
                if (stopwatch.IsRunning && stopwatch.Elapsed > _delay)
                {
                    stopwatch.Reset();
                    _stuck = _stuck ? false : _startLocation == StyxWoW.Me.Location;
                }

                //Start next watch
                if (!stopwatch.IsRunning)
                {
                    _startLocation = StyxWoW.Me.Location;
                    stopwatch.Start();
                }

                //Move to target location, but always fly when stuck or far away.
                Move(location, _stuck
                    || StyxWoW.Me.Location.DistanceSqr(location) > 1000
                    || MovementType.FlyDismount == _movementType
                    || MovementType.Fly == _movementType
                );

                yield return RunStatus.Success;
            }

            private bool ActionTaken(bool condition, ref bool actionTaken)
            {
                actionTaken = actionTaken || condition;
                return condition;
            }

            //Move logic that includes mounting up 
            private void Move(WoWPoint location, bool useFlightor)
            {
                if (useFlightor && !StyxWoW.Me.Mounted && Flightor.MountHelper.CanMount)
                {
                    Flightor.MountHelper.MountUp();
                    Logging.Write("Mounting up...");
                }
                else if (StyxWoW.Me.Mounted && !StyxWoW.Me.IsFlying)
                {
                    WoWMovement.Move(WoWMovement.MovementDirection.JumpAscend);
                    WoWMovement.MoveStop(WoWMovement.MovementDirection.JumpAscend);
                    Logging.Write("Jumping up to fly...");
                }
                else if (StyxWoW.Me.Mounted)
                {
                    Flightor.MoveTo(location, 300);
                    Logging.Write("Flying to {0}", location);
                }
                else
                {
                    Navigator.MoveTo(location);
                    Logging.Write("Moving to {0}", location);
                }
            }
        }

        private Composite _behavior_root;

        private const uint SONGBELLSEED = 89233;
        private const uint ENIGMASEED = 85216;
        private static IEnumerable<uint> SeedEntries = new List<uint>
        {
            SONGBELLSEED,
            ENIGMASEED
        };

        private BehaviorVariables _behaviorVariables; 

        public SunsongRanch(Dictionary<string, string> args)
            : base(args)
        {
            try
            {
                string[] seeds = GetAttributeAsArray<string>("Seeds", false, null, null, ",".ToCharArray());
                if (null != seeds)
                {
                }

                uint? seedEntry  = GetAttributeAsNullable<uint>("SeedEntry", false, null, null);
                _behaviorVariables = new BehaviorVariables(
                    (null != seedEntry && SeedEntries.Contains(seedEntry.Value))
                        ? seedEntry.Value
                        : SONGBELLSEED
                );
            }
            catch (Exception except)
            {
                // Maintenance problems occur for a number of reasons.  The primary two are...
                // * Changes were made to the behavior, and boundary conditions weren't properly tested.
                // * The Honorbuddy core was changed, and the behavior wasn't adjusted for the new changes.
                // In any case, we pinpoint the source of the problem area here, and hopefully it
                // can be quickly resolved.
                LogMessage("error", "BEHAVIOR MAINTENANCE PROBLEM: " + except.Message
                                    + "\nFROM HERE:\n"
                                    + except.StackTrace + "\n");
                IsAttributeProblem = true;
            }
        }

        // Updated by SVN during commit using: svn:keywords "Id Revision"
        public override string SubversionId { get { return ("$Id: SungsongRanch.cs 20 2013-04-07 14:54:31Z unknown@etherealtheory.com $"); } }
        public override string SubversionRevision { get { return ("$Revision: 20 $"); } }

        private static WoWPoint STARTLOCATION = new WoWPoint(-183.0518, 645.1188, 168.3876);
        private static WoWPoint MERCHANTLOCATION = new WoWPoint(-266.4417, 602.4956, 167.549);
        private static WoWPoint TANDGLEDLOCATION = new WoWPoint(-158.7611, 643.6568, 199.7644);

        private const uint SONGBELL = 66043;
        private const uint OCCUPIEDSOIL = 60172;
        private const uint STUBBORNWEED = 60153;
        private const uint ENCROACHINGWEED = 60185;
        private const uint BURSTINGSCALLIONS = 63160; // Only simply plants can burst
        private const uint WIGGLINGSONGBELL = 66047;
        private const uint ALLURINGSONGBELL = 66039;
        private const uint RUNTYSONGBELL = 66044;
        private const uint TANGLEDSONGBELL = 66046;
        private const uint SMOTHEREDSONGBELL = 66045;
        private const uint WILDSONGBELL = 66048;
        private const uint PARCHEDSONGBELL = 66042;
        private const uint INFESTEDSONGBELL = 66041;
        private const uint UNTILLEDSOIL = 58562;
        private const uint TILLEDSOIL = 58563;
        private const uint GROWINGSONGBELL = 66040;

        private static IDictionary<uint, IList<uint>> _targetEntries = new Dictionary<uint, IList<uint>>()
        {
            //Order matters to get the next target

            //Troubled First
            { OCCUPIEDSOIL,     null },
            { STUBBORNWEED,     null },
            { ENCROACHINGWEED,  null },
            { WIGGLINGSONGBELL, new List<uint> { 65924, 60029, 66117, 63146, 65993, 63189, 63254, 66020, 63233, 63169, 65971, 66133, 63270, 66010, 66089 } },
            { ALLURINGSONGBELL, new List<uint> { 65913, 60070, 66107, 63147, 65985, 63178, 63243, 66012, 63222, 63159, 65964, 66122, 63259, 66002, 66079 } },
            { RUNTYSONGBELL,    new List<uint> { 65920, 60218, 66114, 63150, 65990, 63186, 63251, 66017, 63230, 63166, 65968, 66130, 63267, 66007, 66086 } },
            { TANGLEDSONGBELL,  new List<uint> { 65922, 60270, 66116, 63151, 65992, 63188, 63253, 66019, 63232, 63168, 65970, 66132, 63269, 66009, 66088 } },
            { SMOTHEREDSONGBELL,new List<uint> { 65921, 60181, 66115, 63428, 65991, 63187, 63252, 66018, 63231, 63167, 65969, 66131, 63268, 66008, 66087 } },
            { WILDSONGBELL,     new List<uint> { 65925, 60207, 66118, 63149, 65994, 63190, 63225, 66021, 63234, 63170, 65972, 66134, 63271, 66011, 66090 } },

            //Full grown
            { BURSTINGSCALLIONS,new List<uint> { 63157, 66108, 63158, 63180, 63245, 63223, BURSTINGSCALLIONS, 66123, 63260, 66080 }}, 
            { SONGBELL,         new List<uint> { 59833, 58567, 66113, 63154, 63185, 63250, 63229, 63165, 66129, 6325, 66085, 65989, 66016, 65973, 66006, /*Enigma->*/ 65933, 65932, 65930, 65938, 65931, 65929,   } },

            //Clean
            { UNTILLEDSOIL,     null },
            { TILLEDSOIL,       null },

            //Multi problem
            { INFESTEDSONGBELL, new List<uint> { 65918, 60026, 66110, 63145, 65987, 63182, 63247, 66014, 63226, 63162, 65966, 66125, 63262, 66004, 66082 } },
            { PARCHEDSONGBELL,  new List<uint> { 65919, 58565, 66111, 63144, 65988, 63283, 63248, 66015, 63227, 63163, 65967, 66127, 63263, 66005, 66083 } },

            //Final
            { GROWINGSONGBELL,  new List<uint> { 65916, 58566, 66109, 63153, 65986, 63181, 63246, 66013, 63224, 63161, 65965, 66124, 63261, 66003, 66081 } }, 
        };

        private class BehaviorVariables
        {
            //static BehaviorVariables()
            //{
            //    //ObjectManager.OnObjectListUpdateFinished += ObjectManager_OnObjectListUpdateFinished;
            //}

            public BehaviorVariables(uint seedEntry)
            {
                _seedEntry = seedEntry;
            }

            private T GetNearestObject<T>(WoWObject target, params uint[] entries) where T : WoWObject
            {
                if (null == target)
                    return null;

                ObjectManager.Update();
                IEnumerable<T> wowObjects = ObjectManager.GetObjectsOfType<T>(true).Where(wgo => entries.Contains(wgo.Entry));
                wowObjects = wowObjects.OrderBy(o => o.Location.DistanceSqr(target.Location));
                return wowObjects.FirstOrDefault();
            }

            private WoWUnit _target = null;
            private WoWUnit GetTarget()
            {
                ObjectManager.Update();
                IOrderedEnumerable<WoWUnit> targets = ObjectManager.GetObjectsOfType<WoWUnit>(true).Where(o => o.IsValid).OrderBy(o => o.DistanceSqr);

                foreach (KeyValuePair<uint, IList<uint>> pair in _targetEntries)
                {
                    WoWUnit target;
                    if (null == (target = targets.FirstOrDefault(o => o.Entry == pair.Key)) && null != pair.Value)
                        foreach (uint entry in pair.Value)
                            if (null != (target = targets.FirstOrDefault(o => o.Entry == entry)))
                                break;

                    if (null != target)
                    {
                        if (_target != target)
                        {
                            _target = target;
                            Logging.Write("Current Target: {0} {1} {2}", target.Entry, target.Name, target.Location);
                        }
                        _entry = pair.Key;
                        return target;
                    }
                }
                return null;
            }

            public WoWUnit Target
            {
                get
                {
                    return GetTarget();
                }

            }

            private uint _entry = 0;
            public uint Entry
            {
                get
                {
                    return _entry;
                }
            }

            private uint _seedEntry = SONGBELLSEED;
            public uint SeedEntry 
            {
                get
                {
                    return _seedEntry;
                }
            }


            private int _tilledSoilCount = 0;
            public int MissingSeeds
            {
                get
                {
                    WoWUnit target = GetTarget();
                    if (null != target && target.DistanceSqr < 1000)
                        _tilledSoilCount = ObjectManager.GetObjectsOfType<WoWUnit>(true).Where(o => o.IsValid && o.Entry == TILLEDSOIL).OrderBy(o => o.DistanceSqr).Count();

                    int count = _tilledSoilCount;
                    foreach (WoWItem wowItem in StyxWoW.Me.BagItems.Where(wi => wi.Entry == SeedEntry))
                        count -= (int)wowItem.StackCount;
                    return Math.Max(count, 0);
                }
            }

            public bool HasTarget
            {
                get
                {
                    return null != Target;
                }
            }

            private const uint PESTREPELLERENTRY1 = 215162;
            private const uint PESTREPELLERENTRY2 = 215163;
            public WoWGameObject PestRepeller
            {
                get
                {
                    return GetNearestObject<WoWGameObject>(Target, PESTREPELLERENTRY1, PESTREPELLERENTRY2);
                }
            }

            private const uint SPRINKLERENTRY1 = 215135;
            private const uint SPRINKLERENTRY2 = 215137;
            public WoWGameObject Sprinkler
            {
                get
                {
                    return GetNearestObject<WoWGameObject>(Target, SPRINKLERENTRY1, SPRINKLERENTRY2);
                }
            }

            private const uint MERCHANTGREENFIELD = 58718;
            public WoWUnit MerchantGreenfield
            {
                get
                {
                    return GetNearestObject<WoWUnit>(StyxWoW.Me, MERCHANTGREENFIELD);
                }
            }
        }

        /// <summary>
        /// Fly to the start location if we do not have a target
        /// </summary>
        /// <returns>starting location behavior</returns>
        private Decorator GetStartDecorator()
        {
            return new Decorator(
                ret => !_behaviorVariables.HasTarget || _behaviorVariables.Entry == GROWINGSONGBELL,
                new MovementAction(
                    ret => STARTLOCATION,
                    MovementAction.MovementType.Fly,
                    TimeSpan.FromSeconds(6),
                    ret => TANDGLEDLOCATION
                )
            );
        }

        /// <summary>
        /// Interacts with a plant to tend to it and make it grow properly. 
        /// </summary>
        /// <param name="target"><see cref="WoWUnit"/> that represents the troubled plant.</param>
        /// <returns>behavior to handle a troubled plant.</returns>
        private Composite GetInteractBehavior()
        {
            return new PrioritySelector(
                GetStartDecorator(),
                new Decorator(
                    ret => StyxWoW.Me.Combat || StyxWoW.Me.IsCasting || StyxWoW.Me.IsChanneling,
                    new ActionAlwaysSucceed()
                ),
                new MovementAction(
                    ret => _behaviorVariables.Target, 
                    MovementAction.MovementType.Dismount,
                    TimeSpan.FromSeconds(6), 
                    ret => TANDGLEDLOCATION
                ),
                new Decorator(
                    ret => StyxWoW.Me.Class == WoWClass.Druid && StyxWoW.Me.Shapeshift != ShapeshiftForm.Normal,
                    new Action(delegate
                    {
                        Lua.DoString("CancelShapeshiftForm()");
                    })
                ),
                new InteractAction(
                    TimeSpan.FromSeconds(8), 
                    ret => _behaviorVariables.Target
                )
            );
        }

        private const int GRABRUNTYCROPID = 116119;
        //private WoWAura _runtyJumpTrigger = null;
        /// <summary>
        /// Pulls a runty plant out of the ground to allow it to grow. 
        /// </summary>
        /// <returns>behavior to handle a runty plant.</returns>
        private Composite GetRuntyBehavior()
        {
            return new PrioritySelector(
                GetStartDecorator(),
                new Decorator(
                    ret => StyxWoW.Me.HasAura(GRABRUNTYCROPID), //null != (StyxWoW.Me.ActiveAuras.Values.FirstOrDefault(aura => aura.SpellId == GRABRUNTYCROPID)),
                    //new Action(delegate{
                    //    WoWAura woWAura = StyxWoW.Me.ActiveAuras.Values.FirstOrDefault(aura => aura.SpellId == GRABRUNTYCROPID);
                    //    if(null != woWAura)
                    //        Logging.Write("{0} {1} {2}", woWAura.SpellId, woWAura.Name, woWAura.TimeLeft);
                    //    if(StyxWoW.Me.HasAura(GRABRUNTYCROPID))
                    //        Logging.Write("Has Aura {0}", GRABRUNTYCROPID);
                    //})
                    new TargetAction(
                        TimeSpan.FromSeconds(3), 
                        ret => _behaviorVariables.Target,
                        delegate
                        {
                            Logging.Write("Pulling {0} out of ground at {1}", _behaviorVariables.Target.Name, _behaviorVariables.Target.Location);
                            WoWMovement.Move(WoWMovement.MovementDirection.JumpAscend);
                            WoWMovement.MoveStop(WoWMovement.MovementDirection.JumpAscend);
                        }
                    )
                ),
                new MovementAction(
                    ret => _behaviorVariables.Target,
                    MovementAction.MovementType.Dismount,
                    TimeSpan.FromSeconds(6),
                    ret => TANDGLEDLOCATION
                ),
                new InteractAction(
                    TimeSpan.FromSeconds(8),
                    ret => _behaviorVariables.Target
                )
            );
        }

        private const int PULLINGTANGLEVINES = 116242;
        /// <summary>
        /// Untangles a plant by flying into the sky and snapping the vine. 
        /// </summary>
        /// <returns>behavior to handle a tangled plant.</returns>
        private Composite GetTangledBehavior()
        {
            return new PrioritySelector(
                GetStartDecorator(),
                new Decorator(
                    ret => null != StyxWoW.Me.ActiveAuras.Values.FirstOrDefault(aura => aura.SpellId == PULLINGTANGLEVINES),
                    new MovementAction(
                        ret => TANDGLEDLOCATION,
                        MovementAction.MovementType.Fly,
                        TimeSpan.FromSeconds(6), 
                        ret => STARTLOCATION
                    )
                ),
                new MovementAction(
                    ret => _behaviorVariables.Target,
                    MovementAction.MovementType.Dismount,
                    TimeSpan.FromSeconds(6),
                    ret => TANDGLEDLOCATION
                ),
                new InteractAction(
                    TimeSpan.FromSeconds(8),
                    ret => _behaviorVariables.Target
                )
            );
        }

        /// <summary>
        /// Fly to Merchant Greenfield for seeds.
        /// </summary>
        /// <returns>seed purchasing behavior</returns>
        private Decorator GetMerchantDecorator()
        {
            return new Decorator(
                ret => _behaviorVariables.MissingSeeds > 0,
                new PrioritySelector(
                    new Decorator(
                        ret => _behaviorVariables.MissingSeeds == 1,
                        new Wait( //We have to build in a delay for missing seeds to update unfortunately...
                            3,
                            ret => _behaviorVariables.MissingSeeds == 0,
                            new ActionAlwaysSucceed()
                        )
                    ),
                    new Decorator(
                        ret => MerchantFrame.Instance.IsVisible && _behaviorVariables.MissingSeeds > 0,
                        new TargetAction(
                            TimeSpan.FromSeconds(8), 
                            ret => _behaviorVariables.MerchantGreenfield,
                            delegate
                            {
                                var items = MerchantFrame.Instance.GetAllMerchantItems();
                                var item = items.FirstOrDefault(i => i.ItemId == _behaviorVariables.SeedEntry
                                    && (i.BuyPrice * (ulong)_behaviorVariables.MissingSeeds) <= StyxWoW.Me.Copper
                                    && (i.NumAvailable >= _behaviorVariables.MissingSeeds || i.NumAvailable == -1));

                                if (item != null)
                                    MerchantFrame.Instance.BuyItem(item.Index, _behaviorVariables.MissingSeeds);
                            }
                        )
                    ),
                    new Decorator(
                        ret => null == _behaviorVariables.MerchantGreenfield,
                        new MovementAction(
                            ret => MERCHANTLOCATION,
                            MovementAction.MovementType.Fly,
                            TimeSpan.FromSeconds(6),
                            ret => STARTLOCATION
                        )
                    ),
                    new MovementAction(
                        ret => _behaviorVariables.MerchantGreenfield,
                        MovementAction.MovementType.FlyDismount,
                        TimeSpan.FromSeconds(6),
                        ret => MERCHANTLOCATION
                    ),
                    new InteractAction(
                        TimeSpan.FromSeconds(8),
                        ret => _behaviorVariables.MerchantGreenfield
                    )
                )
            );
        }

        /// <summary>
        /// Plants the correct seeds in tilled soil, and get seeds when necessary from Merchant GreenField.
        /// </summary>
        /// <returns>behavior to handle tilled soil.</returns>
        private Composite GetTilledSoilBehavior()
        {
            return new PrioritySelector(
                GetMerchantDecorator(),
                GetStartDecorator(),
                new MovementAction(
                    ret => _behaviorVariables.Target,
                    MovementAction.MovementType.Dismount,
                    TimeSpan.FromSeconds(6),
                    ret => TANDGLEDLOCATION
                ),
                new TargetAction(
                    TimeSpan.FromSeconds(5), 
                    ret => _behaviorVariables.Target,
                    delegate
                    {
                        Logging.Write("Planting seed on {0} at {1}", _behaviorVariables.Target.Name, _behaviorVariables.Target.Location);
                        StyxWoW.Me.CarriedItems.FirstOrDefault(i => i.Entry == _behaviorVariables.SeedEntry && i.Cooldown == 0).UseContainerItem();
                    }
                )
            );
        }

        /// <summary>
        /// Removes weed from the plant.
        /// </summary>
        /// <returns>behavior to handle plants with weed.</returns>
        private Composite GetWeedBehavior()
        {
            return new PrioritySelector(
                GetStartDecorator(),
                new Decorator(
                    ret => StyxWoW.Me.InVehicle,
                    new Action(delegate
                        {
                            Logging.Write("Removing weed from {0} at {1}", _behaviorVariables.Target.Name, _behaviorVariables.Target.Location);
                            while (StyxWoW.Me.InVehicle)
                            {
                                Lua.DoString("RunMacroText(\"/click OverrideActionBarButton1\")");
                                Lua.DoString("RunMacroText(\"/click ElvUI_Bar1Button1\")");
                            }
                        }
                    )
                ),
                new MovementAction(
                    ret => _behaviorVariables.Target,
                    MovementAction.MovementType.Dismount,
                    TimeSpan.FromSeconds(6),
                    ret => TANDGLEDLOCATION
                ),
                new InteractAction(
                    TimeSpan.FromSeconds(8), 
                    ret => _behaviorVariables.Target
                )
            );
        }

        /// <summary>
        /// Wrestles with the vine to let the plant grow.
        /// </summary>
        /// <returns>behavior to handle wild plants.</returns>
        private Composite GetWildBehavior()
        {
            return new PrioritySelector(
                GetStartDecorator(),
                new Decorator(
                    ret => StyxWoW.Me.InVehicle,
                    new Action(delegate
                        {
                            Logging.Write("Wrestling with vine on {0} at {1}", _behaviorVariables.Target.Name, _behaviorVariables.Target.Location);
                            while (StyxWoW.Me.InVehicle)
                            {
                                Lua.DoString("RunMacroText(\"/click OverrideActionBarButton1\")");
                                Lua.DoString("RunMacroText(\"/click ElvUI_Bar1Button1\")");
                                //StyxWoW.Me.CurrentTarget.isc
                                Lua.DoString("RunMacroText(\"/click OverrideActionBarButton2\")");
                                Lua.DoString("RunMacroText(\"/click ElvUI_Bar1Button2\")");
                                Thread.Sleep(200);
                            }
                        }
                    )
                ),
                new MovementAction(
                    ret => _behaviorVariables.Target,
                    MovementAction.MovementType.Dismount,
                    TimeSpan.FromSeconds(6),
                    ret => TANDGLEDLOCATION
                ),
                new InteractAction(
                    TimeSpan.FromSeconds(8), 
                    ret => _behaviorVariables.Target
                )
            );
        }

        /// <summary>
        /// Clears up the infestation of the plant.
        /// </summary>
        /// <returns>behavior to handle infested plants.</returns>
        private Composite GetInfestedBehavior()
        {
            return new PrioritySelector(
                GetStartDecorator(),
                new MovementAction(
                    ret => _behaviorVariables.PestRepeller,
                    MovementAction.MovementType.Dismount,
                    TimeSpan.FromSeconds(6),
                    ret => TANDGLEDLOCATION
                ),
                new InteractAction(
                    TimeSpan.FromSeconds(8), 
                    ret => _behaviorVariables.PestRepeller
                )
            );
        }

        /// <summary>
        /// Sprinkles water on the parched plants.
        /// </summary>
        /// <returns>behavior to handle parched plants.</returns>
        private Composite GetParchedBehavior()
        {
            return new PrioritySelector(
                GetStartDecorator(),
                new MovementAction(
                    ret => _behaviorVariables.Sprinkler,
                    MovementAction.MovementType.Dismount,
                    TimeSpan.FromSeconds(6),
                    ret => TANDGLEDLOCATION
                ),
                new InteractAction(
                    TimeSpan.FromSeconds(8), 
                    ret => _behaviorVariables.Sprinkler
                )
            );
        }

        /// <summary>
        /// Return the behavior tree for Sungsong Ranch to manage the field and plant Songbell's
        /// </summary>
        /// <returns>Sungsong Ranch behavior</returns>
        protected override Composite CreateBehavior()
        {
            return (_behavior_root ?? (_behavior_root =
                new Switch<uint>( 
                    ret => _behaviorVariables.Entry,
                    GetStartDecorator(),
                    new SwitchArgument<uint>(BURSTINGSCALLIONS, GetInteractBehavior()),
                    new SwitchArgument<uint>(SONGBELL, GetInteractBehavior()),

                    new SwitchArgument<uint>(OCCUPIEDSOIL, GetInteractBehavior()),
                    new SwitchArgument<uint>(WIGGLINGSONGBELL, GetInteractBehavior()),
                    new SwitchArgument<uint>(ALLURINGSONGBELL, GetInteractBehavior()),
                        
                    new SwitchArgument<uint>(STUBBORNWEED, GetWeedBehavior()),
                    new SwitchArgument<uint>(ENCROACHINGWEED, GetWeedBehavior()),
                    new SwitchArgument<uint>(SMOTHEREDSONGBELL, GetWeedBehavior()),

                    new SwitchArgument<uint>(TANGLEDSONGBELL, GetTangledBehavior()),
                    new SwitchArgument<uint>(RUNTYSONGBELL, GetRuntyBehavior()),
                    new SwitchArgument<uint>(WILDSONGBELL, GetWildBehavior()),
                        
                    new SwitchArgument<uint>(UNTILLEDSOIL, GetInteractBehavior()),
                    new SwitchArgument<uint>(TILLEDSOIL, GetTilledSoilBehavior()),

                    new SwitchArgument<uint>(INFESTEDSONGBELL, GetInfestedBehavior()),
                    new SwitchArgument<uint>(PARCHEDSONGBELL, GetParchedBehavior()),

                    new SwitchArgument<uint>(GROWINGSONGBELL, GetStartDecorator()) //new ActionAlwaysSucceed())
                )
            ));
        }

        /// <summary>
        /// Returns true if the field has been fully tilled and Songbells are growing.
        /// </summary>
        public override bool IsDone
        {
            get
            {
                return GROWINGSONGBELL == _behaviorVariables.Entry;
            }
        }

    }
}

